﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;

namespace NetFrameworkExtensions.Mathematics
{
  public class EMAAccumulator
  {
    double mean;
    double mean2;
    double lambda;
    double k;

    public EMAAccumulator(double lambda, double startingMean = 0)
    {
      Contract.Requires(lambda > 0);
      Contract.Requires(lambda < 1);
      mean = startingMean;
      this.lambda = lambda;
      k = CalculateKFromLambda(lambda);
    }

    public static EMAAccumulator FromLambda(double lambda, double startingMean = 0)
    {
      return new EMAAccumulator(lambda, startingMean);
    }

    public static EMAAccumulator FromK(double k, double startingMean = 0)
    {
      Contract.Requires(k > 0);
      return new EMAAccumulator(CalculateLambdaFromK(k), startingMean);
    }

    public void Add(double value, int steps = 1)
    {
      Contract.Requires(steps >= 1);
      double wMean = steps == 1 ? lambda : Math.Pow(lambda, steps);
      mean = wMean * mean + (1 - wMean) * value;
      mean2 = wMean * mean2 + (1 - wMean) * value * value;
    }

    public double Mean { get { return mean; } }
    public double Variance
    {
      get
      {
        var m2 = mean * mean;
        return m2 > mean2 ? 0 : mean2 - m2;
      }
    }
    public double StandardDeviation { get { return Math.Sqrt(Variance); } }
    public double Lambda { get { return lambda; } }
    public double K { get { return k; } }

    /// <summary>
    /// Calculate the normalized weight of the mean respect of a new value added to the series
    /// </summary>
    /// <param name="k">Means the amount of succesive values that represents half weight ot the total EMA history. For example, creating an EMA "equivalent" to a 30 SMA, use k=15.</param>
    /// <returns></returns>
    public static double CalculateLambdaFromK(double k)
    {
      Contract.Requires(k > 0);
      return Math.Pow(0.5, 1/k);
    }

    public static double CalculateKFromLambda(double lambda)
    {
      Contract.Requires(lambda > 0);
      Contract.Requires(lambda < 1);
      return -Math.Log(2, lambda);
    }
  }
}
